import { AstContext } from "./context";
export interface Visitor<C = any> {
    visit(ast: AutoJs.UiObject, ctx: any): any;
}
export class MultiVisitor implements Visitor {
    visitors: AstVisitor[] = []
    constructor(visitors: AstVisitor[]) {
        this.visitors = visitors;
    }
    private filterMethods(name: string): Function[] {
        return this.visitors.map(v => v.getMethod(name)).filter(it => !!it)
    }
    visit(root: AutoJs.UiObject, ctx: AstContext): AstContext {
        const queue: AutoJs.UiObject[] = []
        queue.push(root)
        while (queue.length > 0) {
            const top = queue.pop()
            if (!top) continue;
            const childCount = top.childCount()
            for (let i = 0; i < childCount; i++) {
                const node = top.child(i)
                if (node) queue.push(node)
            }
            const result = this.callMethod(top, ctx)
            if (!result && top !== root) {
                top.recycle()
            }
        }
        return ctx

    }

    private callMethod(ast: AutoJs.UiObject, ctx: AstContext) {
        const className = ast.className() || ""
        const classNames = className.split('.')
        const methodName = classNames.pop()
        if (methodName) {
            const methods = this.filterMethods(methodName)
            let result = false
            for (const method of methods) {
                if (method(ast, ctx)) {
                    result = true
                }
            }
            return result
        }
        return false;
    }
}
export class AstVisitor implements Visitor {
    count: number = 0;
    visit(root: AutoJs.UiObject, ctx: AstContext): AstContext {
        const queue: AutoJs.UiObject[] = []
        queue.push(root)
        while (queue.length > 0) {
            const top = queue.pop()
            if (!top) continue
            const isTarget = this.callMethod(top, ctx)
            if (!isTarget) {
                const childCount = top.childCount()
                for (let i = 0; i < childCount; i++) {
                    const child = top.child(i)
                    if (!child) continue
                    queue.push(child)
                }
            }
            if (!isTarget && top !== root) {
                top.recycle()
            }
            this.count += 1;
        }
        return ctx
    }
    toHtml(root: AutoJs.UiObject, ctx: AstContext): AstContext {
        const queue: AutoJs.UiObject[] = []
        queue.push(root)
        while (queue.length > 0) {
            const top = queue.pop()
            if (!top) continue;
            const childCount = top.childCount()
            for (let i = 0; i < childCount; i++) {
                const node = top.child(i)
                if (node) queue.push(node)
            }
            const result = this.callMethod(top, ctx)
            if (!result && top !== root) {
                top.recycle()
            }
        }
        return ctx
    }
    private callMethod(ast: AutoJs.UiObject, ctx: AstContext) {
        const className = ast.className() || ""
        const classNames = className.split('.')
        const methodName = classNames.pop()
        if (methodName) {
            const method = (this as any)[`visit${methodName}`]
            if (method && typeof method === 'function') {
                return method.call(this, ast, ctx)
            }
        }
        return false
    }
    getMethod(name: string) {
        const method = (this as any)[`visit${name}`]
        if (method) return method.bind(this)
    }
}


export function visit(ast: AutoJs.UiObject, visitor: Visitor, ctx: AstContext): AstContext {
    return visitor.visit(ast, ctx)
}